﻿//
// Copyright 2011 Kazuki Oikawa
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.IO;
using System.Text;

namespace MsgPack {
    public class MsgPackReader {
        Stream _strm;
        byte[] _tmp0 = new byte[8];
        byte[] _tmp1 = new byte[8];

        Encoding _encoding = Encoding.UTF8;
        Decoder _decoder = Encoding.UTF8.GetDecoder();
        byte[] _buf = new byte[64];

        public MsgPackReader(Stream strm) {
            _strm = strm;
        }

        public TypePrefixes Type { get; private set; }

        public bool ValueBoolean { get; private set; }
        public uint Length { get; private set; }

        public uint ValueUnsigned { get; private set; }
        public ulong ValueUnsigned64 { get; private set; }

        public int ValueSigned { get; private set; }
        public long ValueSigned64 { get; private set; }

        public float ValueFloat { get; private set; }
        public double ValueDouble { get; private set; }

        public bool IsSigned() {
            return this.Type == TypePrefixes.NegativeFixNum ||
                this.Type == TypePrefixes.PositiveFixNum ||
                this.Type == TypePrefixes.Int8 ||
                this.Type == TypePrefixes.Int16 ||
                this.Type == TypePrefixes.Int32;
        }

        public bool IsBoolean() {
            return this.Type == TypePrefixes.True || this.Type == TypePrefixes.False;
        }

        public bool IsSigned64() {
            return this.Type == TypePrefixes.Int64;
        }

        public bool IsUnsigned() {
            return this.Type == TypePrefixes.PositiveFixNum ||
                this.Type == TypePrefixes.UInt8 ||
                this.Type == TypePrefixes.UInt16 ||
                this.Type == TypePrefixes.UInt32;
        }

        public bool IsUnsigned64() {
            return this.Type == TypePrefixes.UInt64;
        }

        public bool IsRaw() {
            return this.Type == TypePrefixes.FixRaw || this.Type == TypePrefixes.Raw16 || this.Type == TypePrefixes.Raw32;
        }

        public bool IsArray() {
            return this.Type == TypePrefixes.FixArray || this.Type == TypePrefixes.Array16 || this.Type == TypePrefixes.Array32;
        }

        public bool IsMap() {
            return this.Type == TypePrefixes.FixMap || this.Type == TypePrefixes.Map16 || this.Type == TypePrefixes.Map32;
        }

        public bool Read() {
            byte[] tmp0 = _tmp0, tmp1 = _tmp1;
            int x = _strm.ReadByte();
            if (x < 0)
                return false; // EOS

            if (x >= 0x00 && x <= 0x7f) {
                this.Type = TypePrefixes.PositiveFixNum;
            } else if (x >= 0xe0 && x <= 0xff) {
                this.Type = TypePrefixes.NegativeFixNum;
            } else if (x >= 0xa0 && x <= 0xbf) {
                this.Type = TypePrefixes.FixRaw;
            } else if (x >= 0x90 && x <= 0x9f) {
                this.Type = TypePrefixes.FixArray;
            } else if (x >= 0x80 && x <= 0x8f) {
                this.Type = TypePrefixes.FixMap;
            } else {
                this.Type = (TypePrefixes)x;
            }

            switch (this.Type) {
                case TypePrefixes.Nil:
                    break;
                case TypePrefixes.False:
                    ValueBoolean = false;
                    break;
                case TypePrefixes.True:
                    ValueBoolean = true;
                    break;
                case TypePrefixes.Float:
                    _strm.Read(tmp0, 0, 4);
                    if (BitConverter.IsLittleEndian) {
                        tmp1[0] = tmp0[3];
                        tmp1[1] = tmp0[2];
                        tmp1[2] = tmp0[1];
                        tmp1[3] = tmp0[0];
                        ValueFloat = BitConverter.ToSingle(tmp1, 0);
                    } else {
                        ValueFloat = BitConverter.ToSingle(tmp0, 0);
                    }
                    break;
                case TypePrefixes.Double:
                    _strm.Read(tmp0, 0, 8);
                    if (BitConverter.IsLittleEndian) {
                        tmp1[0] = tmp0[7];
                        tmp1[1] = tmp0[6];
                        tmp1[2] = tmp0[5];
                        tmp1[3] = tmp0[4];
                        tmp1[4] = tmp0[3];
                        tmp1[5] = tmp0[2];
                        tmp1[6] = tmp0[1];
                        tmp1[7] = tmp0[0];
                        ValueDouble = BitConverter.ToDouble(tmp1, 0);
                    } else {
                        ValueDouble = BitConverter.ToDouble(tmp0, 0);
                    }
                    break;
                case TypePrefixes.NegativeFixNum:
                    ValueSigned = (x & 0x1f) - 0x20;
                    break;
                case TypePrefixes.PositiveFixNum:
                    ValueSigned = x & 0x7f;
                    ValueUnsigned = (uint)ValueSigned;
                    break;
                case TypePrefixes.UInt8:
                    x = _strm.ReadByte();
                    if (x < 0)
                        throw new FormatException();
                    ValueUnsigned = (uint)x;
                    break;
                case TypePrefixes.UInt16:
                    if (_strm.Read(tmp0, 0, 2) != 2)
                        throw new FormatException();
                    ValueUnsigned = ((uint)tmp0[0] << 8) | (uint)tmp0[1];
                    break;
                case TypePrefixes.UInt32:
                    if (_strm.Read(tmp0, 0, 4) != 4)
                        throw new FormatException();
                    ValueUnsigned = ((uint)tmp0[0] << 24) | ((uint)tmp0[1] << 16) | ((uint)tmp0[2] << 8) | (uint)tmp0[3];
                    break;
                case TypePrefixes.UInt64:
                    if (_strm.Read(tmp0, 0, 8) != 8)
                        throw new FormatException();
                    ValueUnsigned64 = ((ulong)tmp0[0] << 56) | ((ulong)tmp0[1] << 48) | ((ulong)tmp0[2] << 40) | ((ulong)tmp0[3] << 32) | ((ulong)tmp0[4] << 24) | ((ulong)tmp0[5] << 16) | ((ulong)tmp0[6] << 8) | (ulong)tmp0[7];
                    break;
                case TypePrefixes.Int8:
                    x = _strm.ReadByte();
                    if (x < 0)
                        throw new FormatException();
                    ValueSigned = (sbyte)x;
                    break;
                case TypePrefixes.Int16:
                    if (_strm.Read(tmp0, 0, 2) != 2)
                        throw new FormatException();
                    ValueSigned = (short)((tmp0[0] << 8) | tmp0[1]);
                    break;
                case TypePrefixes.Int32:
                    if (_strm.Read(tmp0, 0, 4) != 4)
                        throw new FormatException();
                    ValueSigned = (tmp0[0] << 24) | (tmp0[1] << 16) | (tmp0[2] << 8) | tmp0[3];
                    break;
                case TypePrefixes.Int64:
                    if (_strm.Read(tmp0, 0, 8) != 8)
                        throw new FormatException();
                    ValueSigned64 = ((long)tmp0[0] << 56) | ((long)tmp0[1] << 48) | ((long)tmp0[2] << 40) | ((long)tmp0[3] << 32) | ((long)tmp0[4] << 24) | ((long)tmp0[5] << 16) | ((long)tmp0[6] << 8) | (long)tmp0[7];
                    break;
                case TypePrefixes.FixRaw:
                    Length = (uint)(x & 0x1f);
                    break;
                case TypePrefixes.FixArray:
                case TypePrefixes.FixMap:
                    Length = (uint)(x & 0xf);
                    break;
                case TypePrefixes.Raw16:
                case TypePrefixes.Array16:
                case TypePrefixes.Map16:
                    if (_strm.Read(tmp0, 0, 2) != 2)
                        throw new FormatException();
                    Length = ((uint)tmp0[0] << 8) | (uint)tmp0[1];
                    break;
                case TypePrefixes.Raw32:
                case TypePrefixes.Array32:
                case TypePrefixes.Map32:
                    if (_strm.Read(tmp0, 0, 4) != 4)
                        throw new FormatException();
                    Length = ((uint)tmp0[0] << 24) | ((uint)tmp0[1] << 16) | ((uint)tmp0[2] << 8) | (uint)tmp0[3];
                    break;
                default:
                    throw new FormatException();
            }
            return true;
        }

        public int ReadValueRaw(byte[] buf, int offset, int count) {
            return _strm.Read(buf, offset, count);
        }

        public string ReadRawString() {
            return ReadRawString(_buf);
        }

        public unsafe string ReadRawString(byte[] buf) {
            if (this.Length < buf.Length) {
                if (ReadValueRaw(buf, 0, (int)this.Length) != this.Length)
                    throw new FormatException();
                return _encoding.GetString(buf, 0, (int)this.Length);
            }

            // Poor implementation
            byte[] tmp = new byte[(int)this.Length];
            if (ReadValueRaw(tmp, 0, tmp.Length) != tmp.Length)
                throw new FormatException();
            return _encoding.GetString(tmp);
        }
    }
}
